﻿/* 
 * Copyright (c) 2009 Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;

namespace VerseSharp
{
    /// <summary>
    /// Design by contract validator
    /// </summary>
    public static class Check
    {
        /// <summary>
        /// The specified statement/parameter must be true.
        /// </summary>
        /// <param name="statement">statement/parameter to evaluate.</param>
        /// <param name="messageOrParamName">Name of the message or param.</param>
        /// <exception cref="CheckException">If statement is not true.</exception>
        public static void True(bool statement, string messageOrParamName)
        {
            if (statement)
                return;

            Throw(messageOrParamName, "'{0}' must evaluate as true.");
        }

        /// <summary>
        /// The specified statement/parameter must be false.
        /// </summary>
        /// <param name="statement">statement/parameter to evaluate.</param>
        /// <param name="messageOrParamName">Name of the message or param.</param>
        /// <exception cref="CheckException">If statement is true.</exception>
        public static void False(bool statement, string messageOrParamName)
        {
            if (!statement)
                return;

            Throw(messageOrParamName, "'{0}' must evaluate as false.");
        }

        /// <summary>
        /// Two values can't be equal.
        /// </summary>
        /// <param name="value">value/constant to compare to.</param>
        /// <param name="paramValue">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        /// <remarks><paramref name="value"/> and <paramref name="paramValue"/> are both required.</remarks>
        public static void NotEqual(object value, object paramValue, string messageOrParamName)
        {
            Require(value, "value");
            Require(paramValue, messageOrParamName);
            if (value.Equals(paramValue))
                Throw(messageOrParamName, "'{0}' is required.", value.ToString());
        }

        /// <summary>
        /// Value must be between (or equal) min and max
        /// </summary>
        /// <param name="min">minimum value.</param>
        /// <param name="max">maximum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Between(int min, int max, int value, string messageOrParamName)
        {
            if (value >= min && value <= max)
                return;

            Throw(messageOrParamName, "'{0}' must be between {1} and {2}.", min.ToString(), max.ToString());
        }

        /// <summary>
        /// Betweens the specified min.
        /// </summary>
        /// <param name="min">minimum value.</param>
        /// <param name="max">maximum value.</param>
        /// <param name="value">parameter value to check. May not be null.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Between(int min, int max, string value, string messageOrParamName)
        {
            Between(min, max, value, messageOrParamName, true);
        }

        /// <summary>
        /// Betweens the specified min.
        /// </summary>
        /// <param name="min">minimum value.</param>
        /// <param name="max">maximum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <param name="required"><paramref name="value"/> may be null if this parameter is false.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Between(int min, int max, string value, string messageOrParamName, bool required)
        {
            if (required) Require(value, messageOrParamName);
            if (value == null || value.Length >= min && value.Length <= max)
                return;

            Throw(messageOrParamName, "'{0}' must be between {1} and {2} characters.", min.ToString(), max.ToString());
        }

        /// <summary>
        /// Checks if the value is equal or larger.
        /// </summary>
        /// <param name="min">minimum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Min(DateTime min, DateTime value, string messageOrParamName)
        {
            if (value >= min)
                return;

            Throw(messageOrParamName, "{0} must be larger or equal to {1}.", min.ToString());
        }

        /// <summary>
        /// Checks if the value is equal or larger.
        /// </summary>
        /// <param name="min">minimum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Min(int min, int value, string messageOrParamName)
        {
            if (value >= min)
                return;

            Throw(messageOrParamName, "{0} must be larger or equal to {1}.", min.ToString());
        }

        /// <summary>
        /// Checks if the value is equal or larger.
        /// </summary>
        /// <param name="min">minimum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Min(long min, long value, string messageOrParamName)
        {
            if (value >= min)
                return;

            Throw(messageOrParamName, "{0} must be larger or equal to {1}.", min.ToString());
        }

        /// <summary>
        /// Checks if the value is equal or larger.
        /// </summary>
        /// <param name="min">minimum value.</param>
        /// <param name="value">parameter value (may not be null).</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Min(int min, string value, string messageOrParamName)
        {
            Min(min, value, messageOrParamName, true);
        }

        /// <summary>
        /// Checks if the value is equal or larger.
        /// </summary>
        /// <param name="min">minimum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <param name="required"><paramref name="value"/> may be null if this parameter is false.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Min(int min, string value, string messageOrParamName, bool required)
        {
            if (required) Require(value, messageOrParamName);
            if (value == null || value.Length >= min)
                return;

            Throw(messageOrParamName, "{0} must be larger or equal to {1} characters.", min.ToString());
        }

        /// <summary>
        /// Checks if the value is less or equal.
        /// </summary>
        /// <param name="max">maximum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Max(int max, int value, string messageOrParamName)
        {
            if (value <= max)
                return;

            Throw(messageOrParamName, "{0} must be less or equal to {1}.", max.ToString());
        }

        /// <summary>
        /// Checks if the value is less or equal.
        /// </summary>
        /// <param name="max">maximum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Max(int max, string value, string messageOrParamName)
        {
            Max(max, value, messageOrParamName, true);
        }

        /// <summary>
        /// Checks if the value is less or equal.
        /// </summary>
        /// <param name="max">maximum value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <param name="required"><paramref name="value"/> may be null if this parameter is false.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Max(int max, string value, string messageOrParamName, bool required)
        {
            if (required) Require(value, messageOrParamName);
            if (value == null || value.Length <= max)
                return;

            Throw(messageOrParamName, "{0} must be less or equal to {1} characters.", max.ToString());
        }

        /// <summary>
        /// Checks if the value is less or equal.
        /// </summary>
        /// <param name="max">max value.</param>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Max(DateTime max, DateTime value, string messageOrParamName)
        {
            if (value <= max)
                return;

            Throw(messageOrParamName, "{0} must be larger or equal to {1}.", max.ToString());
        }

        /// <summary>
        /// Parameter is required (may not be null).
        /// </summary>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void Require(object value, string messageOrParamName)
        {
            if (value != null)
                return;

            Throw(messageOrParamName, "'{0}' is required.");
        }

        /// <summary>
        /// The specified string may not be null or empty.
        /// </summary>
        /// <param name="value">parameter value.</param>
        /// <param name="messageOrParamName">parameter name, or a error message.</param>
        /// <exception cref="CheckException">If contract fails.</exception>
        public static void NotEmpty(string value, string messageOrParamName)
        {
            if (!string.IsNullOrEmpty(value))
                return;

            Throw(messageOrParamName, "'{0}' must not be empty.");
        }

        private static void Throw(string messageOrParamName, string message, params string[] arguments)
        {
            string[] args = new string[arguments.Length + 1];
            arguments.CopyTo(args, 1);
            args[0] = messageOrParamName;

            if (messageOrParamName.IndexOf(' ') == -1)
                throw new CheckException(string.Format(message, args), args[0]);
            else
                throw new CheckException(messageOrParamName, args[0]);
        }

        /// <summary>
        /// Check if the specified type can be assigned from the parameter type.
        /// </summary>
        /// <param name="type">Type that the parameter must be  (or derive/implement).</param>
        /// <param name="instance">instance</param>
        /// <param name="messageOrParamName">error message or parameter name</param>
        public static void Type(Type type, object instance, string messageOrParamName)
        {
            if (type.IsAssignableFrom(instance.GetType()))
                return;

            Throw(messageOrParamName, "'{1}' must be assignable from {0}.", type.ToString());
        }
    }

    /// <summary>
    /// Exception thrown when a validation fails.
    /// </summary>
    public class CheckException : ArgumentException
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CheckException"/> class.
        /// </summary>
        /// <param name="msg">Formatted message.</param>
        /// <param name="paramName">Parameter name.</param>
        internal CheckException(string msg, string paramName)
            : base(msg, paramName)
        {
        }
    }
}
